/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package central;

import java.io.*;
import java.net.*;
import java.sql.Timestamp;
//import java.util.logging.Level;
import java.util.logging.*;

/**
 *
 * @author SchabauerJ
 */
public class Transmitter extends Thread {

    private Logger logger = null;
    //IP Einstellungen
    private static DatagramSocket clientSocket = null;
    private InetAddress IPAddress = null;
    private int IPPort = 8888;  //default
    public Integer sendInterval = 100;  //Send Interval for Telegrams in ms
    long roundTripTime= 1000;  //ms
    //Zwischenspeicher für Kommandos / Servostates
    char cmd;  //Testet alle Servos, bis ein Button gedrückt wird
    short servoState[] = new short[4]; //Zeigt den Zustand aller Servos in Grad (0..180Grad)
    int deviceState = 0;               //Speichert den Zustand der IOs wie Hupe, Licht, LED ...
    //Zu sendendes Telegramm
    private byte[] currentTel = new byte[1024];
    private final static int ARDUINO_UDP_TX_PACKET_MAX_SIZE= 24;

    public class ReceivedTel {

        byte type = '0';
        long packageTime = 0;
        int deviceStatus = 0;
        short ADC_Values[] = new short[6];
    }
    ReceivedTel receivedTel= new ReceivedTel();

    enum Mode {

        sender, receiver
    };
    public Mode runMode;
    private ByteArrayOutputStream bos = null;
    private DataOutputStream dos = null;
    private DataInputStream dis = null;


    /*
     * Konstruktor
     */
    Transmitter(Mode mode) {
        runMode = mode;
        cmd = 'x';
        bos = new ByteArrayOutputStream();
        //dos = new DataOutputStream(bos);
        logger = Logger.getLogger("Transmitter as " + mode);
        logger.info("Object 'Transmitter' initialized");
    }

    public void connect(String ip_addr, int port) {
        //BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));
        try {
            if (clientSocket == null) {
                clientSocket = new DatagramSocket();
            }
            IPAddress = InetAddress.getByName(ip_addr);
            IPPort = port;
            this.start();

        } catch (SocketException ex) {
            Logger.getLogger(UDP_Client.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(UDP_Client.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void disconnect() {
        clientSocket.close();
    }

    private byte[] prepareCurrentTel() {
        try {
            dos = new DataOutputStream(bos);
            dos.writeByte(cmd);
            long timeMillis = System.currentTimeMillis();
            dos.writeLong(timeMillis);
            if (cmd == 'R') {
                dos.writeInt(deviceState);
                for (short servo : servoState) {
                    dos.writeShort(servo);
                }
            }
            
            int remainingBytes= ARDUINO_UDP_TX_PACKET_MAX_SIZE - dos.size();
            if(remainingBytes < 0)
            {
               logger.warning("Telegramm war zu lang: "+remainingBytes+"Bytes, "+ARDUINO_UDP_TX_PACKET_MAX_SIZE+ "erlaubt");
            }
    
            for (int i=0; i<remainingBytes; i++)
            {
                dos.writeByte(0x00);
            }

            dos.flush();
            currentTel = bos.toByteArray();
            bos.reset();

        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
        return currentTel;
    }

    /*
     * private byte[] receivedTel() { try { long timeMillis =
     * System.currentTimeMillis(); ByteArrayInputStream bos = new
     * ByteArrayInputStream(); DataInputStream dos = new DataInputStream(bos);
     * dos.readLong(timeMillis); dos.writeShort(IPPort); dos.writeBytes(cmd);
     * dos.flush(); currentTel = bos.toByteArray(); }
     *
     * catch (IOException ex) { logger.log(Level.SEVERE, null, ex); } return
     * currentTel; }
     */

    /*
     * User Interface (API)
     */
    public void setCMD(String _cmd) {
        cmd = _cmd.charAt(0);
    }

    public void setCMD(char _cmd) {
        cmd = _cmd;
    }
    
    /*
     * RUN runs as sender or receiver, acording to the runMode, the thread was
     * constructed with.
     */
    @Override
    public void run() {
        logger.info("Thread 'UDP_Client' created and running");
        if (runMode == Mode.sender) {
            runAsSender();
        } else {
            runAsReceiver();
        }
    }

    /*
     * runAsSender: The purpose of this thread is to continuously poll the
     * servoStates and send a telegram to the client
     */
    private void runAsSender() {
        while (true) {
            try {
                byte[] sendData = prepareCurrentTel();
                //byte[] receiveData = new byte[1024];
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, IPPort);
                clientSocket.send(sendPacket);
                logger.info("SENT: '" + sendData.toString() + "'");
                //DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                //clientSocket.receive(receivePacket);
                Thread.sleep(sendInterval);
            } catch (InterruptedException ex) {
                logger.log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                logger.log(Level.SEVERE, null, ex);
            }
        }
    }

    private void runAsReceiver() {
        while (true) {
            try {
                DatagramPacket receivePacket = new DatagramPacket(currentTel, currentTel.length);
                clientSocket.receive(receivePacket);
                long receiveTime= System.currentTimeMillis();
                String modifiedSentence = new String(receivePacket.getData());
                logger.info("RECEIVED: '" + modifiedSentence + "'");
                ByteArrayInputStream bis = new ByteArrayInputStream(currentTel);
                //Der Anfang des Telegrams ist mit einem BigEndian Input Stream zu interpretieren,
                //weil der (bigEndian) TimeStamp vom Arduino ohne Interpretation vom Empfangsbuffer in den Sendebuffer
                //kopiert wird.
                dis = new DataInputStream(bis);
                receivedTel.type = dis.readByte();  //Bei bytes gibt es ja sowieso keine Probleme
                receivedTel.packageTime = dis.readLong();
                
                //Und jetzt kommt leider Little Endian :(
                //weil der Arduino eben Little Endian verwendet und die ADC-Werte
                //direkt vom ADC-Arrays des Arduinos kopiert werden
                //Achtung: der Byte Input Stream ist eh schon weiter gelaufen, sodass
                //man nicht wieder von null weg lesen muss.  Man kann jetzt sofort mit
                //dem short Array für die ADC-Werte weiter machen.
                LittleEndianDataInputStream ldis= new LittleEndianDataInputStream(bis);
                
                //byte b= ldis.readByte();  //Genau das hier kann man sich sparen
                //long l= ldis.readLong();

                //receivedTel.deviceStatus = dis.readInt();  //Wo ist denn der deviceStatus geblieben?
                for (int i = 0; i < 6; i++) {
                    receivedTel.ADC_Values[i] = ldis.readShort();
                }
                roundTripTime= receiveTime-receivedTel.packageTime;
                logger.log(Level.INFO, "RoundTripTime was {0}ms", roundTripTime);
    
            } catch (IOException ex) {
                logger.log(Level.SEVERE, null, ex);
            }
        }
    }
}